<template>
  <span :style="[{ fontSize, fontWeight: bold ? 'bold' : 'normal', color }, curStyle]">
    {{ beasData.displayValue }}
  </span>
</template>

<script setup lang="ts">
  import type { CSSProperties } from 'vue';
  const props = defineProps({
    /** #是否强制保留几位小数 (如：2.00是否需要舍去.00) */
    forcedDecimals: { type: Boolean, default: true },
    /** #分隔符(千分位分隔符)如：1,000,000.00 中分隔符为逗号 */
    separator: { type: String, default: ',' },
    /** #显示的前缀 */
    prefix: { type: String, default: '' },
    /** #显示的后缀 */
    suffix: { type: String, default: '' },
    /** 开始的数值，默认从0增长到某一个数 */
    startVal: { type: Number, default: 0 },
    /** 要滚动的目标数值，必须 */
    endVal: { type: Number, default: 0 },
    /** 滚动到目标数值的动画持续时间，单位为毫秒(ms) */
    duration: { type: Number, default: 1200 },
    /** 设置数值后是否自动开始滚动 */
    autoplay: { type: Boolean, default: true },
    /** 保留几位小数(数字精度)  */
    precision: { type: [String, Number], default: 2 },
    /** 是否在即将到达目标数值的时候，使用缓慢滚动的效果 */
    useEasing: { type: Boolean, default: true },
    /** 字体颜色 */
    color: { type: String, default: 'var(--el-text-color-primary)' },
    /** 字体大小 */
    fontSize: { type: String, default: '24px' },
    /** 是否加粗字体 */
    bold: { type: Boolean, default: false },
    /** 自定义Style */
    curStyle: { type: [Object, String, Array] as PropType<CSSProperties | CSSProperties[] | string[] | string>, default: undefined },
  });

  const emit = defineEmits<{
    (e: 'start'): void;
    (e: 'end'): void;
    (e: 'stateChange', state: 1 | 2 | 3 | 4): void;
  }>();

  const beasData = shallowReactive({
    localStartVal: props.startVal,
    displayValue: formatNumberFun(props.startVal), // 在页面中显示的值
    printVal: 0,
    paused: false,
    localDuration: props.duration,
    startTime: 0,
    timestamp: 0,
    remaining: 0,
    rAF: 0,
  });
  /** 当前状态; 1代表未播放,2代表播放中,3代表暂停播放,4代表播放完成 */
  const state = computed<1 | 2 | 3 | 4>(() => {
    return props.startVal === beasData.printVal ? 1 : props.endVal === beasData.printVal ? 4 : beasData.paused ? 3 : 2;
  });
  watch(
    () => state.value,
    (_state) => {
      emit('stateChange', _state);
    }
  );

  /**
   * 格式化数据
   */
  function formatNumberFun(numb: number) {
    const newNumb = numb.toFixed(Number(props.precision));
    // 把数字分割为整数和小数部分
    const x = newNumb.split('.');
    // 整数部分
    let x1 = x[0] || '';
    // 小数部分(包含小数点)，没有小数部分这为孔字符串
    let x2 = Number(x[1] || '') === 0 && !props.forcedDecimals ? '' : `.${x[1] || ''}`;
    // 如果没有强制保留几位小数，且结束值为整数那么就舍去小数部分
    x2 = !props.forcedDecimals && props.endVal % 1 === 0 ? '' : x2;
    // 设置千分位分隔符
    const rgx = /(\d+)(\d{3})/;
    if (x1 && props.separator && isNaN(Number(props.separator))) {
      while (rgx.test(x1)) {
        x1 = x1.replace(rgx, `$1${props.separator}$2`);
      }
    }
    return `${props.prefix}${x1}${x2}${props.suffix}`;
  }

  const countDown = computed(() => props.startVal > props.endVal);
  watch(
    () => [props.startVal, props.endVal],
    () => {
      props.autoplay && startFun();
    }
  );

  // 挂载完成后
  onMounted(() => {
    props.autoplay && startFun();
  });

  // 卸载组件
  onUnmounted(() => {
    cancelAnimationFrame(beasData.rAF);
  });

  /** 暂停 */
  function pause() {
    beasData.rAF && cancelAnimationFrame(beasData.rAF);
    beasData.paused = true;
  }

  /**
   * 数字跳动
   */
  function startFun() {
    beasData.localStartVal = props.startVal;
    beasData.startTime = 0;
    beasData.localDuration = props.duration;
    beasData.paused = false;
    emit('start');
    beasData.rAF && cancelAnimationFrame(beasData.rAF);
    beasData.rAF = requestAnimationFrame(countFun);
  }

  /** 当为停状态下时继续滚动；当为播放状态下则暂停 */
  function restart() {
    if (beasData.paused) {
      resume();
      beasData.paused = false;
    } else {
      pause();
      beasData.paused = true;
    }
  }

  /**
   * 继续(暂停的情况下)
   */
  function resume() {
    if (!beasData.remaining) {
      return;
    }
    beasData.startTime = 0;
    beasData.localDuration = beasData.remaining;
    beasData.localStartVal = beasData.printVal;
    beasData.rAF = requestAnimationFrame(countFun);
  }
  /**
   * 重置
   */
  function reset() {
    beasData.printVal = props.startVal;
    beasData.startTime = 0;
    beasData.rAF && requestAnimationFrame(countFun);
    beasData.displayValue = formatNumberFun(props.startVal);
  }

  /**
   * 进行数字跳动
   */
  function countFun(timestamp: number) {
    if (!beasData.startTime) {
      beasData.startTime = timestamp;
    }
    beasData.timestamp = timestamp;
    const progress = timestamp - beasData.startTime;
    beasData.remaining = beasData.localDuration - progress;

    if (props.useEasing) {
      if (countDown.value) {
        beasData.printVal = beasData.localStartVal - easingFn(progress, 0, beasData.localStartVal - props.endVal, beasData.localDuration);
      } else {
        beasData.printVal = easingFn(progress, beasData.localStartVal, props.endVal - beasData.localStartVal, beasData.localDuration);
      }
    } else if (countDown.value) {
      beasData.printVal = beasData.localStartVal - (beasData.localStartVal - props.endVal) * (progress / beasData.localDuration);
    } else {
      beasData.printVal = beasData.localStartVal + (props.endVal - beasData.localStartVal) * (progress / beasData.localDuration);
    }
    if (countDown.value) {
      beasData.printVal = beasData.printVal < props.endVal ? props.endVal : beasData.printVal;
    } else {
      beasData.printVal = beasData.printVal > props.endVal ? props.endVal : beasData.printVal;
    }

    beasData.displayValue = formatNumberFun(beasData.printVal);
    if (progress < beasData.localDuration) {
      beasData.rAF = requestAnimationFrame(countFun);
    } else {
      cancelAnimationFrame(beasData.rAF);
      emit('end');
    }
  }

  /**
   * 动画函数
   */
  function easingFn(t: number, b: number, c: number, d: number) {
    return (c * (-(2 ** ((-10 * t) / d)) + 1) * 1024) / 1023 + b;
  }

  defineExpose({
    /** 播放 */
    play: startFun,
    /** 暂停 */
    pause,
    /** 继续 */
    resume,
    /** 重置 */
    reset,
    /** 当为停状态下时继续滚动；当为播放状态下则暂停 */
    restart,
    /** 得到状态; 1代表未播放,2代表播放中,3代表暂停播放,4代表播放完成 */
    getState(): 1 | 2 | 3 | 4 {
      return state.value;
    },
  });
</script>
